#pragma once

#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "../comm/log.hpp"
#include "../comm/Util.hpp"

/**********************************
 * 运行模块就是执行生成的可执行可执行文件
 * 我们只关心运行是否出现异常
 *--->因为运行对不对有我们自己判断(测试用例)
 ***********************************/
namespace ns_runner
{
    using namespace ns_Util;
    using namespace ns_log;
    class Runner
    {
    public:
        Runner() {}
        ~Runner() {}

    public:
    //设置接口表示进程占用资源大小
        static void SetProcLimit(int cpu_limit, int mem_limit)
        {
            
            // 设置cpu限制
            struct rlimit _cpu_lim;
            _cpu_lim.rlim_max = RLIM_INFINITY;
            _cpu_lim.rlim_cur = cpu_limit;
            setrlimit(RLIMIT_CPU, &_cpu_lim);

            // 设置内存限制
            struct rlimit _mem_lim;
            _mem_lim.rlim_max = RLIM_INFINITY;
            _mem_lim.rlim_cur = mem_limit * 1024; // 转化为kb
            setrlimit(RLIMIT_AS, &_mem_lim);
        }
        /***********************************
         * cpu_lim: 该程序运行的时候，可以使用的最大cpu资源上限
         * mem_lim: 改程序运行的时候，可以使用的最大的内存大小(KB)
         * 
         * 程序运行结果
         * 1.运行完成，结果正确
         * 2.运行完成，结果不正确
         * 3.运行出现异常
         * 程序运行结束，我们只需要考虑3是否出现异常。
         *
         * 返回结果
         * <0   :内部错误
         * =0   :正常执行，有对应的执行文件
         * >0   :程序异常,返回信号
         *
         * 程序默认启动时
         * 标准输入:不处理
         * 标准输出:程序执行完，输出运行结果
         * 标准错误:运行时的错误信息
         ************************************/
        static int Run(const string &file_name,int cpu_lim,int mem_lim) // 只需要传入文件名，不需要传路径和程序，因为传入路径更冗余
        {
            
            string _file_excute = PathUtil::Exe(file_name);
            string _stdin = PathUtil::Stdin(file_name);
            string _stdout = PathUtil::Stdout(file_name);
            string _stderr = PathUtil::Stderr(file_name);
            // 文件描述符(重定向到文件中)
            umask(0);
            int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0644);
            int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);

            if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
            {
                LOG(ERROR) << "打开标准文件失败" << "\n";
                return -1;
            }
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << "子进程创建失败！" << "\n";
                ::close(_stdin_fd);
                ::close(_stdout_fd);
                ::close(_stderr_fd);
                return -2;
            }
            else if (pid > 0)
            {
                ::close(_stdin_fd);
                ::close(_stdout_fd);
                ::close(_stderr_fd);
                int status = 0;
                waitpid(pid, &status, 0);
                // 程序异常一定出现了信号
                LOG(INFO) << "运行完成,info:" << (status & 0x7f) << "\n";
                return status & 0x7f; // 返回信号
            }
            else
            {
                // 将标准输入输出报错重定向到文件中
                dup2(_stdin_fd, 0);
                dup2(_stdout_fd, 1);
                dup2(_stderr_fd, 2);

                SetProcLimit(cpu_lim,mem_lim);

                execl(_file_excute.c_str() /*在哪里执行*/, _file_excute.c_str() /*我想在命令行上如何执行该程序*/, nullptr);
                exit(1);
            }
        }
    };
}